#ifndef SNAKE_HPP
#define SNAKE_HPP

#include <QQuickPaintedItem>
#include <QPainter>
#include <QTimer>
#include <QDateTime>
#include <QRandomGenerator>
#include <QDebug>


/// 贪吃蛇
class Snake : public QQuickPaintedItem
{
    Q_OBJECT
private:
    enum DIRECTION: int{
        LEFT = 0,
        UP,
        RIGHT,
        DOWN
    };
    struct Node{
        size_t x;
        size_t y;
        Node* pre;
        Node* next;
        Node():x(0),y(0),pre(nullptr),next(nullptr){}
        friend QDebug operator<<(QDebug d, const Node& n)  {
            d << "(x:" << n.x << ", y:" << n.y << " pre:" << n.pre << " next" << n.next <<  ")";
            return d;
        }
    };
    const constexpr static int   interval[4] = {100, 80, 60, 40};
    const constexpr static int   grid[4] = {100, 80, 60, 40};

public:
    Snake(QQuickItem* parent=nullptr)
        :QQuickPaintedItem(parent)
        ,m_timer(this)
        ,m_direct(LEFT)
        ,m_failed(false)
        ,m_head(new Node())
        ,m_tail(new Node())
        ,m_mark(nullptr)
        ,m_bug_pos(0, 0)
        ,m_grid(grid[0])
    {
        m_head->next = m_tail;
        m_tail->pre = m_head;
        resetHeadTailVar();
        srandBug();
        m_timer.setInterval(interval[0]);
        m_timer.setSingleShot(false);
        /* 每隔固定时间刷新一次(走一步) */
        connect(&m_timer, &QTimer::timeout, this, [this](){update();});
        reinit();
    }

    ~Snake() override{
        while(m_head){
            m_tail = m_head;
            m_head = m_head->next;
            delete m_tail;
        }
        while(m_mark){
            m_tail = m_mark;
            m_mark = m_mark->next;
            delete m_tail;
        }
    }

public:
    Q_INVOKABLE void pause() {m_timer.stop();}
    Q_INVOKABLE void restart(int gridIndex, int levelIndex) {
        if(gridIndex < 4 && levelIndex < 4){
                m_timer.stop();
                m_timer.setInterval(interval[levelIndex]);
                m_grid = grid[gridIndex];
                reinit();
                m_timer.start();
        }
    }
    Q_INVOKABLE void start() {m_timer.start();}
    Q_INVOKABLE void setDirect(int d){
       if(m_direct % 2 == d % 2 || d > DOWN) return;
        m_direct = d;
    }

protected:
    virtual void paint(QPainter* p) override
    {
        /* 去锯齿等平滑设置 */
        p->setRenderHints(QPainter::Antialiasing| QPainter::TextAntialiasing| QPainter::SmoothPixmapTransform);
        QGradient nightFade;
    #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
        /* 部分版本暂无该构造函数,下方方法比较通用 */
        // https://webgradients.com/
        nightFade = QGradient::NightFade;
    #else
        nightFade = QLinearGradient(0, 0, 0, 1);
        nightFade.setSpread(QGradient::PadSpread);
        nightFade.setCoordinateMode(QGradient::ObjectBoundingMode);
        nightFade.setColorAt(0, QColor(251, 188,235));
        nightFade.setColorAt(1.0, QColor(161, 140, 209));
    #endif
        p->fillRect(QRectF(0, 0, width(), height()), nightFade);
        if(!m_failed && m_timer.isActive()) m_failed = !move();
        if(m_failed){
            p->setPen(Qt::red);
            QFont font("Microsoft YaHei");
            font.setPixelSize(16);
            font.setBold(true);
            p->setFont(font);
            p->drawText(QRectF(0, 0, width(), height()), Qt::AlignCenter, "GAME OVER");
        }else{
            p->scale(width()/m_grid, height()/m_grid);
            p->setPen(Qt::NoPen);
            p->setBrush(Qt::blue);
            p->drawRect(QRectF(m_bug_pos.first, m_bug_pos.second, 1, 1));
            p->setBrush(Qt::black);
            auto ptr = m_head;
            while (ptr) {
                p->drawRect(QRectF(ptr->x, ptr->y, 1, 1));
                ptr = ptr->next;
            }
        }
    }

private:
    void reinit(){
        m_failed = false;
        if(m_head->next == m_tail){
            srandBug();
            resetHeadTailVar();
            return;
        }

        m_tail->pre->next = nullptr;
        m_tail->pre = nullptr;

        if(m_mark == nullptr){
           m_mark = m_head->next;
           m_mark->pre = nullptr;
        }else{
            auto mtail = m_mark;
            while (mtail->next) {
                mtail = mtail->next;
            }
            mtail->next = m_head->next;
            mtail->next->pre = mtail;
        }

        m_head->next = m_tail;
        m_tail->pre = m_head;
        m_head->pre = nullptr;
        m_tail->next = nullptr;
        srandBug();
        resetHeadTailVar();

    }

    void resetHeadTailVar(){
        m_head->x = static_cast<size_t>(QRandomGenerator::global()->bounded(m_grid/3, m_grid-1));
        m_head->y = static_cast<size_t>(QRandomGenerator::global()->bounded(m_grid/3, m_grid-1));
        m_tail->x = m_head->x+1;
        m_tail->y = m_head->y;
        m_direct = LEFT;
    }

    Node* getNode(){
        if(m_mark==nullptr) return new Node();
        auto ret = m_mark;
        m_mark = m_mark->next;
        if(m_mark)m_mark->pre = nullptr;
        return ret;
    }

    bool hit(size_t x, size_t y) const{
        if(x >= static_cast<size_t>(m_grid) || y >= static_cast<size_t>(m_grid)) return true;
        auto ptr = m_head;
        while(ptr){
            if(ptr->x == x && ptr->y == y) return true;
            ptr = ptr->next;
        }
        return false;
    }

    bool move(){
        size_t x = m_head->x;
        size_t y = m_head->y;
        switch (m_direct) {
        case LEFT:{
            x--;
            break;
        }
        case UP:{
            y--;
            break;
        }
        case RIGHT:{
            x++;
            break;
        }
        case DOWN:{
            y++;
            break;
        }
        }
        if(hit(x, y)) return false;
        if(x == m_bug_pos.first && y == m_bug_pos.second){
            Node* node = getNode();
            node->x = m_bug_pos.first;
            node->y = m_bug_pos.second;

            node->pre = nullptr;
            node->next = m_head;

            m_head->pre = node;
            m_head = node;
            srandBug();
        }else{
            auto ptr = m_tail;

            m_tail = m_tail->pre;
            m_tail->next = nullptr;

            ptr->pre = nullptr;
            ptr->next = nullptr;
            ptr->x = x;
            ptr->y = y;

            ptr->next = m_head;
            m_head->pre = ptr;
            m_head = ptr;

        }
        return true;
    }

    inline void srandBug(){
        /* 可以生成点在蛇身上,不做其它检验 */
        m_bug_pos = QPair<size_t, size_t>(
                    static_cast<size_t>(QRandomGenerator::global()->bounded(m_grid)),
                    static_cast<size_t>(QRandomGenerator::global()->bounded(m_grid))
                    );
    }

private:
    QTimer                      m_timer;
    int                         m_direct;
    bool                        m_failed;
    /*
     * 数据量小,可以采用qtl或者stl容器取代此处原生链表[没做太多测试]
     * 如 QList<QPair<size_t, size_t>> 、
     * std::vector<std::pair<size_t, size_t>>
     * 逻辑简单且效率问题忽略不计
    */
    Node*                       m_head;
    Node*                       m_tail;
    Node*                       m_mark;
    QPair<size_t, size_t>       m_bug_pos;
    int                         m_grid;
};

#endif // SNAKE_HPP
